package com.duojuhe.aspect;

import com.alibaba.fastjson.JSONObject;
import com.duojuhe.common.annotation.KeyLock;
import com.duojuhe.common.constant.SingleStringConstant;
import com.duojuhe.common.constant.SystemConstants;
import com.duojuhe.common.utils.jsonutils.JsonUtils;
import com.duojuhe.common.utils.page.PageHelperUtil;
import com.duojuhe.redis.redisson.RedisSonLockUtils;
import com.duojuhe.common.exception.base.DuoJuHeException;
import com.duojuhe.common.result.ErrorCodes;
import com.duojuhe.common.result.ServiceResult;
import com.duojuhe.common.utils.encryption.md5.MD5Util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.redisson.api.RLock;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 并发锁
 *
 * @date 2018/3/6.
 */
@Slf4j
@Aspect
@Component
public class KeyLockServiceAspect {
    private static final String lockAllKey = "allKey";
    /**
     * 定义哪些方法需要处理
     */
    @Pointcut("execution(* com.duojuhe..*.*(..))")
    public void servicePoint() {

    }

    //拦截被 @keyLock 修饰的方法,加并发锁，当前并发锁是本地缓存，后期集群部署时改造成分布式锁,一般建议对如果并发会导致数据问题的才加，普通操作不建议加
    @Around("(servicePoint() && @annotation(keyLock))")
    @ResponseBody
    public ServiceResult checkLock(ProceedingJoinPoint pjp, KeyLock keyLock) throws Throwable {
        log.debug("【service执行】目标方法名为:{},所属类的类名:{}", pjp.getSignature().getName(), pjp.getSignature().getDeclaringTypeName());
        //生成分布式锁key的键名，以逗号分隔
        //当前线程名
        String threadName = Thread.currentThread().getName();
        log.info("线程{}------进入并发锁aop------", threadName);
        String keyParts = keyLock.lockKeyParts();
        //获取参数列表
        Object[] objs = pjp.getArgs();
        //默认使用全局key
        String beforeKey = lockAllKey;
        if (objs.length > 0 && StringUtils.isNotBlank(keyParts) && !beforeKey.equals(keyParts)) {
            if (keyLock.fixedKey()) {
                //使用固定key
                beforeKey = keyParts;
            } else {
                beforeKey = getKeyByJson(objs, keyParts);
            }
        }else{
            if (keyLock.fixedKey()) {
                //使用固定key
                beforeKey = keyParts;
            }else{
                //把参数拼接起来
                beforeKey = getKeyByParameter(objs, keyParts);
            }
        }
        if (StringUtils.isBlank(beforeKey)) {
            beforeKey = lockAllKey;
        }
        log.info("线程{}，要加锁未加密的原始key={}", threadName, beforeKey);
        //把ke转成md5
        String beforeKeyMd5 = MD5Util.getMD532(beforeKey);
        //拼接redis key
        String key = SystemConstants.Lock_KEY+beforeKeyMd5;
        log.info("线程{}，要加锁的key={}，加密前：{}，加密后：{}", threadName,key, beforeKey,beforeKeyMd5);
        //如果该值为0则一直等待
        int waitTime = keyLock.waitTime();
        if(waitTime<=0){
            //加一直等待锁
            //获取锁
            RLock lock = null;
            try {
                lock = RedisSonLockUtils.fairLock(key);
                log.info("线程{}，获取锁成功key={}，加密前：{}", threadName,key,beforeKey);
                return (ServiceResult) pjp.proceed();
            }catch (DuoJuHeException e) {
                // 自定义异常直接抛出
                log.debug("线程{}，获取锁失败key={}，加密前：{}，系统自定义异常", threadName,key,beforeKey,e);
                throw e;
            }catch (Exception e){
                log.info("线程{}，获取锁失败key={}，加密前：{}，系统异常", threadName,key,beforeKey,e);
                throw new DuoJuHeException(ErrorCodes.FAIL);
            }finally {
                log.info("线程{}，释放锁key={}，加密前：{}", threadName,key,beforeKey);
                RedisSonLockUtils.unlock(lock);
            }
        }else{
            //加等待指定时间，如果未获取到，自动显示超时
            if (RedisSonLockUtils.fairTryLock(key,waitTime)) {
                try {
                    log.info("线程{}，获取锁成功key={}，加密前：{}", threadName,key,beforeKey);
                    return (ServiceResult) pjp.proceed();
                }catch (DuoJuHeException e) {
                    // 自定义异常直接抛出
                    log.debug("线程{}，获取锁失败key={}，加密前：{}，系统自定义异常", threadName,key,beforeKey,e);
                    throw e;
                }catch (Exception e){
                    log.info("线程{}，获取锁失败key={}，加密前：{}，系统异常", threadName,key,beforeKey,e);
                    throw new DuoJuHeException(ErrorCodes.FAIL);
                } finally {
                    log.info("线程{}，释放锁key={}，加密前：{}", threadName,key,beforeKey);
                    RedisSonLockUtils.unlock(key);
                }
            } else {
                log.info("线程{}，获取锁失败key={}，加密前：{}", threadName,key,beforeKey);
                return ServiceResult.fail(ErrorCodes.REQUEST_TOO_MANY_PEOPLE_ERROR);
            }
        }
    }



    /**
     * 在目标方法执行后执行
     *
     * @param joinPoint
     */
    @After("(servicePoint())")
    public void After(final JoinPoint joinPoint) {
        log.debug("【service之后执行】目标方法名为:{},所属类的类名:{}", joinPoint.getSignature().getName(), joinPoint.getSignature().getDeclaringTypeName());
        PageHelperUtil.clearPage();
    }

    /**
     * 获取字符串
     * @param objs
     * @return
     */
    private String getStringBuilderString(Object[] objs,String keyParts){
        StringBuilder str = new StringBuilder();
        for (Object s : objs) {
            str.append(s);
        }
        return str.toString();
    }


    /**
     * 获取key
     * @param objs
     * @param keyParts
     * @return
     */
    private String getKeyByJson(Object[] objs, String keyParts) {
        try{
            //生成并发锁key
            StringBuilder keyBuffer = new StringBuilder();
            for (Object obj:objs){
                if (!JsonUtils.isJson(obj.toString())){
                    continue;
                }
                keyBuffer.append(getKeyByJsonByObject(obj,keyParts));
            }
            return keyBuffer.toString();
        }catch (Exception e){
            return getKeyByParameter(objs,keyParts);
        }
    }



    /**
     * 获取key
     * @param obj
     * @param keyParts
     * @return
     */
    private String getKeyByJsonByObject(Object obj, String keyParts) {
        //因为只有一个JSON参数，直接取第一个
        JSONObject param = JSONObject.parseObject(obj.toString());
        //生成并发锁key
        StringBuilder keyBuffer = new StringBuilder();
        String[] keyPartArray = keyParts.split(SingleStringConstant.COMMA);
        for (String keyPart : keyPartArray) {
            if (keyPart.contains(".")) {
                String[] keys = keyPart.split("\\.");
                JSONObject jsonObject = null;
                for (int i = 0; i < keys.length - 1; i++) {
                    if (!StringUtils.isEmpty(keys[i])) {
                        jsonObject = param.getJSONObject(keys[i]);
                    }
                }
                if (jsonObject != null) {
                    keyBuffer.append(jsonObject.getString(keys[keys.length - 1]));
                }
            } else {
                keyBuffer.append(param.getString(keyPart));
            }
        }
        return keyBuffer.toString();
    }

    /**
     * 获取key
     * @param objs
     * @return
     */
    private String getKeyByParameter(Object[] objs, String keyParts) {
        try {
            return getStringBuilderString(objs,keyParts);
        }catch (Exception e){
            return "";
        }
    }

}
